package com.flow.framework.common.json.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.flow.framework.common.enumeration.CodeEnum;
import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.util.verify.VerifyUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义 code enum 反序列化
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/1/2
 */
@Slf4j
@JacksonStdImpl
public class CodeEnumDeserializer extends StdScalarDeserializer<CodeEnum> implements ContextualDeserializer {

    private Class<? extends CodeEnum> propertyClazz;

    private final Map<String, CodeEnum> codeAndCodeEnumMap = new ConcurrentHashMap<>(16);

    private volatile boolean initialized = false;

    /**
     * 反序列化时会先调用空构造创建实例并调用com.flow.framework.common.json.serializer.CodeEnumSerializer.createContextual
     * 创建实际用于反序列化的对象，且不同枚举类都会分别只调用一次
     */
    public CodeEnumDeserializer() {
        super(CodeEnum.class);
    }

    public CodeEnumDeserializer(Class<? extends CodeEnum> clazz) {
        super(clazz);
        propertyClazz = clazz;
    }

    @Override
    public CodeEnum deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException {
        String value = p.getText();
        if (null == value) {
            return null;
        }
        if (!initialized) {
            synchronized (codeAndCodeEnumMap) {
                if (!initialized) {
                    CodeEnum[] enumConstants = propertyClazz.getEnumConstants();
                    if (!VerifyUtil.isEmpty(enumConstants)) {
                        for (CodeEnum codeEnum : enumConstants) {
                            codeAndCodeEnumMap.put(codeEnum.getCode(), codeEnum);
                        }
                    }
                    initialized = true;
                }
            }
        }
        CodeEnum codeEnum = codeAndCodeEnumMap.get(value);
        if (null == codeEnum) {
            log.error("can't find enum. enum name: {}, value: {}", propertyClazz.getSimpleName(), value);
            throw new CheckedException(SystemErrorCode.OBJECT_NOT_FOUND_ERROR);
        }
        return codeEnum;
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
        return new CodeEnumDeserializer((Class<? extends CodeEnum>) property.getType().getRawClass());
    }
}
